﻿using GalaSoft.MvvmLight.Messaging;
using GalaSoft.MvvmLight.Threading;
using JiveMessenger.Log;
using JiveMessenger.Models;
using JiveMessenger.Network;
using JiveMessenger.Utilities;
using JiveMessenger.ViewModels;
using JmShared;
using Newtonsoft.Json;
using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.ComponentModel;
using System.Diagnostics;
using System.Globalization;
using System.IO;
using System.Linq;
using System.Reflection;
using System.Runtime.InteropServices.WindowsRuntime;
using System.Threading;
using System.Threading.Tasks;
using Windows.ApplicationModel;
using Windows.Networking.PushNotifications;
using Windows.Storage;
using Windows.UI;
using Windows.UI.Core;
using Windows.UI.Notifications;
using Windows.UI.Xaml;

namespace JiveMessenger.User
{
    public class Client : IClient, INotifyPropertyChanged
    {
        //private ManualResetEvent _receivedClientListEvent = new ManualResetEvent(false);
        //private ManualResetEvent _addedCurrentUserEvent = new ManualResetEvent(false);
        private CancellationTokenSource _cts;
        private string _lastConnectedServer;
        private readonly object MessageQueueLockObject = new object();
        private Dictionary<int, Dictionary<int, ChatMessage>> _queuedMessages;
        private Dictionary<int, int> _processedMessageCount;
        private Dictionary<int, List<ChatMessage>> _addedBatchMessages;
        private static readonly Logger Logger = LogManager.GetCurrentClassLogger();
        private bool _isPlayingNotificationSound;
        public string LoggedInUserID { get; set; }
        private object _lockObject = new object();
        private volatile bool _stopReconnectThread;
        //private Dictionary<string, string> _defaultEmoticonDic;
        private bool _isFirstConnect = true;
        private bool _receivedClientList = false;
        private DispatcherTimer _receiptTimer;
        private Dictionary<string, IEmoticon> SymbolEmoticons;
        private ConcurrentQueue<UserInfo> AvatarAndEmoticonCheckQueue { get; set; }
        private ConcurrentQueue<ContactEmoticonTransfer> ReceiveEmoticonQueue { get; set; }
        private Task ReceiveEmoticonTask { get; set; }
        private Task AvatarAndEmoticonCheckTask { get; set; }
        private string _appVersion;
        public string AppVersion
        {
            get
            {
                if (string.IsNullOrEmpty(_appVersion))
                {
                    var version = Package.Current.Id.Version;
                    _appVersion = String.Format("{0}.{1}.{2}.{3}",
    version.Major, version.Minor, version.Build, version.Revision);
                }
                return _appVersion;
            }
        }

        public int MessageID { get; set; }

        private bool _isLoggedIn;
        public bool IsLoggedIn
        {
            get { return _isLoggedIn; }
            set
            {
                if (_isLoggedIn != value)
                {
                    _isLoggedIn = value;
                    RaisePropertyChanged("IsLoggedIn");
                }
            }
        }

        private bool _isAfk;
        public bool IsAfk
        {
            get { return _isAfk; }
            set
            {
                if (_isAfk != value)
                {
                    _isAfk = value;
                    RaisePropertyChanged("IsAfk");
                }
            }
        }

        private bool _isBusy;
        public bool IsBusy
        {
            get { return _isBusy; }
            set
            {
                if (_isBusy != value)
                {
                    _isBusy = value;
                    RaisePropertyChanged("IsBusy");
                }
            }
        }

        public string LastConnectedUserName { get; set; }
        public WpUserSettings UserSettings { get; set; }
        public bool StopReconnectRetrying { get; set; }
        public bool IsUserConnecting { get; set; }
        public bool IsRetrying { get; set; }

        private Dictionary<int, KeyValuePair<DateTime, ChatMessage>> _sendtMessages;
        private Dictionary<int, KeyValuePair<DateTime, ChatMessage>> SentMessages
        {

            get
            {
                if (_sendtMessages == null)
                    _sendtMessages = new Dictionary<int, KeyValuePair<DateTime, ChatMessage>>();

                return _sendtMessages;
            }
            set { _sendtMessages = value; }
        }

        //private ConcurrentDictionary<UserInfo, List<string>> _emoticonExistsCache;
        //private ConcurrentDictionary<UserInfo, List<string>> EmoticonExistsCache
        //{
        //    get
        //    {
        //        if (_emoticonExistsCache == null)
        //            _emoticonExistsCache = new ConcurrentDictionary<UserInfo, List<string>>();
        //        return _emoticonExistsCache;
        //    }
        //    set { _emoticonExistsCache = value; }
        //}

        public ObservableCollection<JiveDefaultEmoticonModel> DefaultEmoticons { get; set; }

        private string _deviceID;
        public string DeviceID
        {
            get
            {
                if (string.IsNullOrEmpty(_deviceID))
                    _deviceID = "wp" + BitConverter.ToString(Windows.System.Profile.HardwareIdentification.GetPackageSpecificToken(null).Id.ToArray()).Replace("-", "");
                return _deviceID;
            }
        }


        /// <summary>
        /// Conversations that are not open, key is GroupID
        /// </summary>
        private ConcurrentDictionary<string, ViewModelChat> _closedConversations;

        public ConcurrentDictionary<string, ViewModelChat> ClosedConversations
        {
            get
            {
                if (_closedConversations == null)
                    _closedConversations = new ConcurrentDictionary<string, ViewModelChat>();
                return _closedConversations;
            }
            set { _closedConversations = value; }
        }

        private NetworkConnection _connection;
        public NetworkConnection Connection
        {
            get
            {
                if (_connection == null)
                {

                    _connection = new NetworkConnection(DeviceID, AppVersion);

                    AttachEvents(Connection);
                }
                return _connection;
            }
            set
            {
                if (_connection != null)
                    DetachEvents(_connection);
                _connection = value;
                if (_connection != null)
                {
                    AttachEvents(_connection);
                }
            }
        }

        public UserInfo CurrentUserInfo
        {
            get
            {
                UserInfo user;
                if (!string.IsNullOrEmpty(LoggedInUserID) && ContactClients.TryGetValue(LoggedInUserID, out user))
                    return user;
                else
                    return null;

            }
        }

        private Dictionary<string, UserInfo> _contactClients;
        public Dictionary<string, UserInfo> ContactClients
        {
            get
            {
                if (_contactClients == null)
                    _contactClients = new Dictionary<string, UserInfo>();
                return _contactClients;
            }
            set { _contactClients = value; }
        }

        private List<string> _friendUsernames;
        public List<string> FriendUsernames
        {
            get
            {
                if (_friendUsernames == null)
                    _friendUsernames = new List<string>();
                return _friendUsernames;
            }
            set { _friendUsernames = value; }
        }


        public void ClearConnectedClients()
        {
            ContactClients.Clear();
            // LoggedInUserID = string.Empty;
        }


        public Client()
        {
            if (Windows.ApplicationModel.DesignMode.DesignModeEnabled)
            {
                return;
            }
            DefaultEmoticons = CreateDefaultEmoticonList();
            SymbolEmoticons = CreateSymbolList();
            AvatarAndEmoticonCheckQueue = new ConcurrentQueue<UserInfo>();
            Window.Current.Activated += Current_Activated;
            AppSettings.Instance.PropertyChanged += AppSettings_PropertyChanged;
        }

        private async void AppSettings_PropertyChanged(object sender, PropertyChangedEventArgs e)
        {
            if (e.PropertyName == "IsToastNotificationEnabledSetting")
            {
                await RegisterPushNotificationsWithServer(AppSettings.Instance.IsToastNotificationEnabledSetting);
            }
        }

        private async void Current_Activated(object sender, WindowActivatedEventArgs e)
        {
            if (IsLoggedIn)
            {
                bool isActive = e.WindowActivationState != CoreWindowActivationState.Deactivated;
                DeviceStatus status = new DeviceStatus(CurrentUserInfo.UserID, isActive);
                await Connection.SendCommand(status, ClientCommands.DeviceStatusUpdate);
            }
        }


        private ObservableCollection<JiveDefaultEmoticonModel> CreateDefaultEmoticonList()
        {
            Dictionary<string[], string> defaultEmoticons = StaticSettings.DefaultEmoticons;
            ObservableCollection<JiveDefaultEmoticonModel> list = new ObservableCollection<JiveDefaultEmoticonModel>();
            foreach (KeyValuePair<string[], string> kvp in defaultEmoticons)
            {
                list.Add(new JiveDefaultEmoticonModel() { EmoticonShortcut = kvp.Key, EmoticonPath = "ms-appx:///Assets/DefaultEmoticons/" + kvp.Value });
            }
            return list;
        }
        private Dictionary<string, IEmoticon> CreateSymbolList()
        {
            return new Dictionary<string, IEmoticon> { 
                { "<line>", new JiveSymbolEmoticonModel(){ EmoticonPath="ms-appx:///Assets/Symbols/line.png", EmoticonShortcut="<line>" } },
                { "<spacer>", new JiveSymbolEmoticonModel(){ EmoticonPath="ms-appx:///Assets/Symbols/spacer.png", EmoticonShortcut="<spacer>" } },
                { "<s_music>", new JiveSymbolEmoticonModel(){ EmoticonPath="ms-appx:///Assets/Symbols/music_left.png", EmoticonShortcut="<s_music>" } },
                { "</s_music>", new JiveSymbolEmoticonModel(){ EmoticonPath="ms-appx:///Assets/ymbols/music_right.png", EmoticonShortcut="</s_music>" } },
                { "<w_music>", new JiveSymbolEmoticonModel(){ EmoticonPath="ms-appx:///Assets/Symbols/music_left.png", EmoticonShortcut="<w_music>" } },
                { "</w_music>",new JiveSymbolEmoticonModel(){ EmoticonPath="ms-appx:///Assets/Symbols/music_right.png" , EmoticonShortcut="</w_music>" } }};

        }
        private void AttachEvents(NetworkConnection connection)
        {
            connection.ChatMessageReceived += Connection_ChatMessageReceived;
            connection.ContactEmoticonReceived += Connection_ContactEmoticonReceived;
            connection.ChatMessageSendt += Connection_ChatMessageSendt;
            connection.ClientListReceived += Connection_ClientListReceived;
            connection.ChatReceiptReceived += Connection_ChatReceiptReceived;
            connection.OnDisconnected += Connection_OnDisconnected;
            connection.UserSettingsReceived += Connection_UserSettingsReceived;
            connection.ServerInfoMessageReceived += Connection_ServerInfoMessageReceived;
            connection.DeviceStatusReceived += Connection_DeviceStatusReceived;
            connection.NotificationListReceived += connection_NotificationListReceived;
            connection.LoginResponseReceived += connection_LoginResponseReceived;
            connection.AvatarReceived += connection_AvatarReceived;
            connection.EmoticonRequestReceived += connection_EmoticonRequestReceived;
            connection.AvatarRequestReceived += connection_AvatarRequestReceived;
            connection.MessageQueueReceived += connection_MessageQueueReceived;
        }


        private void DetachEvents(NetworkConnection connection)
        {
            connection.ChatMessageReceived -= Connection_ChatMessageReceived;
            connection.ContactEmoticonReceived -= Connection_ContactEmoticonReceived;
            connection.ChatMessageSendt -= Connection_ChatMessageSendt;
            connection.ClientListReceived -= Connection_ClientListReceived;
            connection.ChatReceiptReceived -= Connection_ChatReceiptReceived;
            connection.OnDisconnected -= Connection_OnDisconnected;
            connection.UserSettingsReceived -= Connection_UserSettingsReceived;
            connection.ServerInfoMessageReceived -= Connection_ServerInfoMessageReceived;
            connection.DeviceStatusReceived -= Connection_DeviceStatusReceived;
            connection.NotificationListReceived -= connection_NotificationListReceived;
            connection.LoginResponseReceived -= connection_LoginResponseReceived;
            connection.AvatarReceived -= connection_AvatarReceived;
            connection.EmoticonRequestReceived -= connection_EmoticonRequestReceived;
            connection.AvatarRequestReceived -= connection_AvatarRequestReceived;
            connection.MessageQueueReceived -= connection_MessageQueueReceived;
        }

        private async void connection_MessageQueueReceived(object sender, MessageQueueEventArgs e)
        {
            await DispatcherHelper.RunAsync(async () =>
                 {
                     StatusBarHelper.ShowStatusBarProgress("Downloading messages");
                     MessageQueue queue = e.MessageQueue;
                     if (queue == null || queue.Messages == null || queue.Messages.Count == 0)
                         return;
                     // Messenger.Default.Send(new NotificationMessage(this, "LockMainPagePivot"));
                     Messenger.Default.Send(new NotificationMessage(this, queue.ChatID, "BeginMessageQueue"));
                     await Task.Delay(10);
                     ViewModelChat vm = GetOrOpenChatViewModel(queue.Messages.First());
                     List<DisplayMessage> displayMessages = new List<DisplayMessage>();
                     using (TimeIt t = new TimeIt("creating displaymessages from queue"))
                     {

                         int i = 1;

                         foreach (ChatMessage msg in queue.Messages)
                         {
                             msg.MessageRelayTime = TimeZoneInfo.ConvertTime(msg.MessageRelayTime.Value, TimeZoneInfo.Local);
                             StatusBarHelper.UpdateStatusBarProgress(i / queue.MessageCount);
                             bool isLastInBatch = (i == queue.MessageCount);
                             displayMessages.Add(CreateDisplayMessage(msg, isLastInBatch));
                             if (msg.MessageType == MessageType.PrivateMessage)
                             {
                                 Messenger.Default.Send(new NotificationMessageAction<IContact>(this, msg.SenderUsername, ViewModelContactList.MessengerCommands.GetContactFromUsername.ToString(), contact =>
                                 {
                                     contact.UnreadMessageCount++;
                                 }));
                             }
                             else if (msg.MessageType == MessageType.GroupMessage)
                             {
                                 Messenger.Default.Send(new NotificationMessageAction<IContact>(this, ViewModelContactList.MessengerCommands.GetCurrentUserContact.ToString(), contact =>
                                     {
                                         JiveChatGroupModel grp = contact.ChatGroups.FirstOrDefault(x => x.ID == msg.RecipientID) as JiveChatGroupModel;
                                         if (grp != null)
                                         {
                                             grp.UnreadMessageCount++;
                                         }
                                     }));
                             }
                             //ReceiveChatMessage(msg);
                             // Messenger.Default.Send(new NotificationMessage<MessageQueueInfo>(this, queue.ChatID, new MessageQueueInfo(i,queue.MessageCount), "UpdateMessageQueueInfo"));
                             i++;
                             //await Task.Delay(20);
                         }
                     }

                     vm.DisplayMessage(displayMessages);
                     Messenger.Default.Send(new NotificationMessage(this, queue.ChatID, "EndMessageQueue"));
                     StatusBarHelper.HideStatusBarProgress();
                     //  Messenger.Default.Send(new NotificationMessage(this, "UnlockMainPagePivot"));
                     //await Task.Delay(200);
                     //Messenger.Default.Send(new NotificationMessage(this, queue.ChatID, "ScrollToEnd"));
                 });
        }

        private async void connection_AvatarRequestReceived(object sender, AvatarRequestEventArgs e)
        {
            AvatarRequest request = e.AvatarRequest;
            string file = request.Hash + request.Extension;// StaticMethods.CombinePath(StaticSettings.AvatarFolder, this.CurrentUserInfo.UserName, request.Hash + request.Extension);
            try
            {
                if (!string.IsNullOrEmpty(file))
                {
                    byte[] imgArray = await StaticMethods.LoadImageData(file, await StaticMethods.GetAvatarStorageFolderForUser(request.Username)).ConfigureAwait(false);

                    if (imgArray == null || imgArray.Length == 0)
                        return;


                    var avatar = new AvatarTransfer
                    {
                        AvatarByteArray = imgArray,
                        Extension = Path.GetExtension(file),
                        SenderID = CurrentUserInfo.UserID
                    };
                    if (imgArray.Length < 50000)
                    {
                        await Connection.SendCommand(avatar, ClientCommands.Avatar);
                    }
                    else //We need to send in chunks
                    {
                        string data = JsonConvert.SerializeObject(avatar);
                        List<string> chunks = StaticMethods.SplitLongMessage(data, 10000); //We send in chunks of 10KB
                        int i = 1;
                        string id = new ShortGuid(Guid.NewGuid());
                        foreach (string chunk in chunks)
                        {
                            FileSlice slice = new FileSlice() { Data = chunk, SliceCount = chunks.Count, SliceNumber = i, TransferID = id, TransferType = FileTransferType.Avatar };
                            await Connection.SendCommand(slice, ClientCommands.FileTransfer);
                            i++;
                        }
                    }
                }
                else
                    Logger.Error("Avatar request failed, avatar not found for key :" + request.Hash);
            }
            catch (Exception ex)
            {
                Logger.ErrorException("Error sending avatar to server", ex);
            }
        }
        private async void connection_EmoticonRequestReceived(object sender, EmoticonRequestEventArgs e)
        {
            EmoticonRequest request = e.EmoticonRequest;
            var emoticon = this.GetEmoticons(this.CurrentUserInfo.UserID, false, true, false)[request.KeyWord];
            string path = emoticon.EmoticonPath;
            if (!string.IsNullOrEmpty(path))
            {
                byte[] imgArray = await StaticMethods.LoadImageData(path, await StaticMethods.GetEmoticonStorageFolderForUser(e.EmoticonRequest.Username)).ConfigureAwait(false);
                if (imgArray == null || imgArray.Length == 0)
                    return;

                var ce = new ContactEmoticonTransfer
                {
                    KeyWord = request.KeyWord,
                    EmoticonByteArray = imgArray,
                    Extension = Path.GetExtension(path),
                    SenderID = CurrentUserInfo.UserID
                };
                if (imgArray.Length < 50000)
                {
                    await Connection.SendCommand(ce, ClientCommands.CustomEmoticon);
                }
                else //We need to send in chunks
                {
                    string data = JsonConvert.SerializeObject(ce);
                    List<string> chunks = StaticMethods.SplitLongMessage(data, 10000); //We send in chunks of 10KB
                    int i = 1;
                    string id = new ShortGuid(Guid.NewGuid());
                    foreach (string chunk in chunks)
                    {
                        FileSlice slice = new FileSlice() { Data = chunk, SliceCount = chunks.Count, SliceNumber = i, TransferID = id, TransferType = FileTransferType.CustomEmoticon };
                        await Connection.SendCommand(slice, ClientCommands.FileTransfer);
                        i++;
                    }
                }
            }
            else
                Logger.Error("Emoticon request failed, emoticon not found for key :" + request.Username);
        }


        private async void connection_AvatarReceived(object sender, AvatarEventArgs e)
        {
            AvatarTransfer avatar = e.Avatar;
            if (avatar.AvatarByteArray == null)
                return;
            await SaveAvatarToDisk(ContactClients[e.Avatar.SenderID], e.Avatar.AvatarByteArray, e.Avatar.Hash, e.Avatar.Extension);
        }
        public async Task SaveAvatarToDisk(UserInfo senderUser, byte[] data, string hash, string extension)
        {

            string savePath = "";// StaticSettings.AvatarFolder;

            try
            {
                StorageFile file = await StaticMethods.SaveImageToStorage(await StaticMethods.GetAvatarStorageFolderForUser(senderUser.UserName), data, hash + extension);
                savePath = file.Path;
            }
            catch (Exception err)
            {
                Messenger.Default.Send(String.Format("Error saving custom emoticon from {0} : ", senderUser.UserName) + err.Message);
                return;
            }


            //  savePath = StaticMethods.CombinePath(StaticSettings.AvatarFolder, senderUser.UserName,hash + extension);


            try
            {
                //if (StaticMethods.FileExistsSync(savePath))
                //{
                Messenger.Default.Send(new NotificationMessageAction<IContact>(this, senderUser.UserID, "GetContactFromUserID", async contact =>
                {
                    if (contact != null)
                    {
                        await DispatcherHelper.RunAsync(() => contact.Avatar = savePath);//await StaticMethods.ByteArrayToBitmapImage(data));
                    }
                }));
                //}
            }
            catch (Exception err)
            {
                //var viewModelLocator = (ViewModelLocator)Application.Current.Resources["Locator"];
                //ITab currentChat = viewModelLocator.ViewModelMainPage.CurrentActiveViewModel;
                //if (currentChat != null)
                //    this.DisplayGroupMessage(CurrentUserInfo.UserID, String.Format("Error saving avatar from {0} : ", senderUser.UserName) + err.Message, MessageType.Error, DateTime.Now, currentChat.ID);
                Logger.ErrorException(String.Format("Error saving avatar from {0} : ", senderUser.UserName), err);
                return;
            }
        }
        private UserInfo CreateUserInfo(WinRTLoginResponse response = null)
        {
            UserInfo currentUser = null;
            if (response != null)
            {
                currentUser = new UserInfo(response.Username, DeviceID)
                {
                    Avatar = response.UserSettings.Avatar,
                    ChatGroups = response.ClientList.ChatGroups,
                    ClientStatus = ClientStatus.Available,
                    ClientVersion = AppVersion,
                    Color = response.UserSettings.FontHexColor,
                    CustomEmoticons = response.UserSettings.CustomEmoticons ?? new HashSet<ContactEmoticon>(),
                    FontFamily = response.UserSettings.FontFamily,
                    FontStyle = response.UserSettings.FontStyle,
                    FontWeight = response.UserSettings.FontWeight,
                    NickName = response.UserSettings.NickName
                };
            }
            else
            {
                currentUser = new UserInfo(AppSettings.Instance.UsernameSetting, DeviceID)
                {
                    Avatar = null,
                    ChatGroups = new List<ChatGroup>(),
                    ClientStatus = ClientStatus.Available,
                    ClientVersion = AppVersion,
                    Color = StaticMethods.ColorToHexString(Colors.Black),
                    CustomEmoticons = new HashSet<ContactEmoticon>(),
                    FontFamily = "Segoe UI",
                    FontStyle = "Normal",
                    FontWeight = "Normal",
                    NickName = ""
                };

            }
            return currentUser;
        }

        //public async Task AddOfflineUser()
        //{
        //    UserInfo offlineUser = CreateUserInfo();
        //    LoggedInUserID = offlineUser.UserID;
        //  await  AddOrUpdateUsers(offlineUser, false, true);
        //}
        private async void connection_LoginResponseReceived(object sender, LoginResponseEventArgs e)
        {
            await DispatcherHelper.RunAsync(async () =>
                 {
                     WinRTLoginResponse response = e.LoginResponse;

                     if (response.Success)
                     {
                         //await OnUiThread(() =>
                         //     {

                         IsLoggedIn = true;
                         IsUserConnecting = false;
                         UserInfo currentUser = CreateUserInfo(response);
                         LoggedInUserID = currentUser.UserID;
                         //}).ConfigureAwait(false);

                         this.SetUserSettings(response.UserSettings as WpUserSettings);
                         Messenger.Default.Send(new NotificationMessage(this, "LockMainPagePivot"));
                         Messenger.Default.Send(new NotificationMessage(this, "HideContactList"));
                         Messenger.Default.Send(new NotificationMessage(this, ViewModelContactList.MessengerCommands.ClearContactList.ToString()));
                         await Task.Delay(20);
                         await this.AddOrUpdateUsers(currentUser, false, response.ClientList.Clients.Count == 0);

                         await ProcessClientList(response.ClientList, false);
                         Messenger.Default.Send(new NotificationMessage(this, "ShowContactList"));

                         await Connection.SendMessage(new ChatMessage(this.CurrentUserInfo, MessageType.ClientReady));

                         if (CurrentUserInfo.ChatGroups == null)
                             CurrentUserInfo.ChatGroups = new List<ChatGroup>();


                         var viewModelLocator = VmHelper.Locator;


                         //await OnUiThread(() =>
                         //    {
                         StartReceiptTimer();
                         StopReconnectRetrying = false;
                         //viewModelLocator.ViewModelLoginControl.RunOnNetworkConnect();
                         viewModelLocator.ViewModelContactList.RunOnNetworkConnect();
                         viewModelLocator.ViewModelMainPage.RunOnNetworkConnect();

                         // this.DisplayGroupMessage(CurrentUserInfo, CurrentUserInfo.PrettyName + " has been added to the conversation.", DateTime.Now, DisplayMessageOptions.LoginOptions);
                         //}).ConfigureAwait(false);


                         LastConnectedUserName = CurrentUserInfo.UserName;
                         Messenger.Default.Send(new NotificationMessage(this, "UnlockMainPagePivot"));



                         await ResetTileCount();
                         await RegisterPushNotificationsWithServer();
                         if (_isFirstConnect)
                         {
                             AvatarAndEmoticonCheckQueue.Enqueue(CurrentUserInfo);
                             foreach (UserInfo user in response.ClientList.Clients)
                             {
                                 if (user.ClientStatus == ClientStatus.Offline
                                     && response.ClientList.Clients.Any(x => x.UserName == user.UserName && x.ID != user.ID && x.ClientStatus != ClientStatus.Offline))
                                     continue;
                                 if (!AvatarAndEmoticonCheckQueue.Any(x => x.UserName == user.UserName))
                                     AvatarAndEmoticonCheckQueue.Enqueue(user);
                             }

                             if (AvatarAndEmoticonCheckTask == null)
                             {
                                 AvatarAndEmoticonCheckTask = Task.Run(async () =>
                                     {
                                         await Task.Delay(10000);
                                         await ProcessAvatarAndEmoticonCheckQueue();
                                     });
                             }
                         }
                         this._isFirstConnect = false;
                     }
                     else
                     {
                         //   var viewModelLocator = VmHelper.Locator;
                         StopReconnectRetrying = true;
                         //  LoggedInUserID = string.Empty;
                         Messenger.Default.Send(new JiveDialogMessage("Login failed", response.Error));
                     }
                 });
        }

        public async Task RegisterPushNotificationsWithServer(bool pushIsEnabled = true)
        {
            PushNotificationChannel channel = await RegisterPushChannel();
            if (channel != null)
            {
                if (pushIsEnabled == false)
                {
                    channel.Close();
                    await _connection.SendMessage(new ChatMessage(this.CurrentUserInfo, MessageType.SendPushUri, ""));
                }
                else
                {
                    if (channel.Uri != AppSettings.Instance.PushUriSetting)
                    {
                        if (await _connection.SendMessage(new ChatMessage(this.CurrentUserInfo, MessageType.SendPushUri, channel.Uri)))
                            AppSettings.Instance.PushUriSetting = channel.Uri;
                    }
                }
            }
        }

        private async void Connection_DeviceStatusReceived(object sender, DeviceStatusEventArgs e)
        {
            await DispatcherHelper.RunAsync(async () =>
            {
                DeviceStatus status = e.Status;
                UserInfo user;
                if (ContactClients.TryGetValue(status.UserID, out user))
                {
                    user.ClientStatus = status.ClientStatus.GetValueOrDefault(ClientStatus.Available);
                    user.TimestampAfk = status.TimeStampAfk;

                    await AddOrUpdateUsers(user);
                }
            });
        }

        private async void connection_NotificationListReceived(object sender, NotificationListEventArgs e)
        {
            await DispatcherHelper.RunAsync(() =>
            {
                if (e.NotificationList != null)
                {
                    var notificationList = e.NotificationList;
                    var friendInviteNotifications = notificationList.FriendInviteNotifications.Cast<NotificationBase>();
                    var groupInviteNotifications = notificationList.GroupInviteNotifications.Cast<NotificationBase>();
                    var serverMessageNotifications = notificationList.ServerMessageNotifications.Cast<NotificationBase>();
                    var updateNotifications = notificationList.UpdateAvailableNotifications.Cast<NotificationBase>();

                    IEnumerable<NotificationBase> notifications = friendInviteNotifications.Union(groupInviteNotifications).Union(serverMessageNotifications).Union(updateNotifications);
                    Messenger.Default.Send(new NotificationMessage<IEnumerable<NotificationBase>>(this, notifications, "AddNotificationList"));
                }
            });
        }

        private void StartReceiptTimer()
        {
            _receiptTimer = new DispatcherTimer();
            _receiptTimer.Interval = TimeSpan.FromSeconds(3);
            _receiptTimer.Tick += _receiptTimer_Tick;
            _receiptTimer.Start();
        }

        void _receiptTimer_Tick(object sender, object e)
        {
            DispatcherTimer timer = (DispatcherTimer)sender;
            timer.Stop();
            try
            {
                lock (_lockObject)
                {
                    List<int> toRemove = new List<int>();
                    foreach (var v in this.SentMessages.ToDictionary(kvp => kvp.Key, kvp => kvp.Value))
                    {

                        if (DateTime.Now.Subtract(v.Value.Key).TotalSeconds > 6)
                        {
                            //App.Current.Dispatcher.BeginInvoke(() =>
                            //{
                            ViewModelChat vm = (ViewModelChat)VmHelper.Locator.ViewModelMainPage.ChatViewModels.FirstOrDefault(x => x is ViewModelChat && x.ID == v.Value.Value.RecipientID);

                            vm.DisplayMessage(new DisplayMessage(JiveContactModel.RedText, String.Format("Message response timeout. It is possible the message was not received: {0}", v.Value.Value.Message), DisplayMessageOptions.ErrorOptions, DateTime.Now, true));
                            //});
                            toRemove.Add(v.Key);
                        }
                    }
                    foreach (int i in toRemove)
                        this.SentMessages.Remove(i);
                }
            }
            catch (Exception err)
            {
                Logger.ErrorException("Error in receipt timer tick", err);
            }
            finally
            {
                timer.Start();
            }
        }
        private void StopReceiptTimer()
        {
            if (_receiptTimer != null)
                _receiptTimer.Stop();
        }

        private void Connection_ChatMessageSendt(object sender, ChatMessageEventArgs e)
        {
            ChatMessage msg = e.Message;
            if (msg.MessageType == MessageType.GroupMessage || msg.MessageType == MessageType.PrivateMessage)
            {
                SentMessages.Add(msg.MessageID.Value, new KeyValuePair<DateTime, ChatMessage>(DateTime.Now, msg));
            }
        }
        async void Connection_ServerInfoMessageReceived(object sender, ServerInfoMessageEventArgs e)
        {
            await DispatcherHelper.RunAsync(async () =>
             {
                 ServerInfoMessage msg = e.Message;
                 switch (msg.MessageType)
                 {
                     case ServerInfoMessageType.UserInfoUpdated:
                         {
                             UserInfo user = msg.UserInfo;
                             await this.AddOrUpdateUsers(user);
                             break;
                         }
                     case ServerInfoMessageType.ContactAuthenticated:
                         {
                             UserInfo user = msg.UserInfo;
                             var locator = VmHelper.Locator;
                             var groupViewModels = locator.ViewModelMainPage.ChatViewModels.Where(x => x.GetType() == typeof(ViewModelGroupChat));


                             await this.AddOrUpdateUsers(user);
                             //Don't show "has logged on" notice for subsequent logins from the same user.
                             if (ContactClients.Values.Where(x => x.UserName == user.UserName && x.ClientStatus != ClientStatus.Offline).Count() == 1)
                                 this.DisplayGroupMessage(user, user.PrettyName + " has been added to the conversation.", DateTime.Now, DisplayMessageOptions.LoginOptions);


                             break;
                         }
                     case ServerInfoMessageType.ContactDisconnected:
                         {
                             RemoveUser(msg.UserInfo.UserID);
                             break;
                         }
                 }
             });
        }

        private async void Connection_UserSettingsReceived(object sender, UserSettingsEventArgs e)
        {

            await DispatcherHelper.RunAsync(() => this.SetUserSettings(e.Settings as WpUserSettings));

        }
        private async void Connection_ClientListReceived(object sender, ClientListEventArgs e)
        {
            await DispatcherHelper.RunAsync(async () =>
                 {
                     if (!IsLoggedIn)
                         return;

                     await ProcessClientList(e.ClientList, true);

                 });
        }
        private async Task<PushNotificationChannel> RegisterPushChannel()
        {
            PushNotificationChannel channel = null;

            try
            {
                channel = await PushNotificationChannelManager.CreatePushNotificationChannelForApplicationAsync();
            }

            catch (Exception ex)
            {
                // Could not create a channel. 
            }
            return channel;
        }
        private async Task ProcessClientList(ClientList list, bool processAvatarAndEmoticons)
        {
            List<UserInfo> users = list.Clients;
            this.FriendUsernames = list.FriendUsernames;
            if (_receivedClientList)//We only include our own user if we're not logging in. When logging in, the user is already added.
            {
                this.CurrentUserInfo.ChatGroups = list.ChatGroups;
                users.Insert(0, this.CurrentUserInfo);
            }

            await this.AddOrUpdateUsers(users, processAvatarAndEmoticons);
            Messenger.Default.Send(new NotificationMessage("UpdateMyGroups"));
        }
        private void Connection_ChatReceiptReceived(object sender, ChatReceiptEventArgs e)
        {
            AcknowledgeSendtMessageReceipt(e.Receipt.MessageID);
        }
        private void Connection_ContactEmoticonReceived(object sender, ContactEmoticonEventArgs e)
        {
            if (ReceiveEmoticonQueue == null)
                ReceiveEmoticonQueue = new ConcurrentQueue<ContactEmoticonTransfer>();
            if (!ReceiveEmoticonQueue.Contains(e.Emoticon))
                ReceiveEmoticonQueue.Enqueue(e.Emoticon);
            lock (_lockObject)
            {
                if (ReceiveEmoticonTask == null)
                    ReceiveEmoticonTask = Task.Run(() => ProcessReceiveEmoticonQueue());
            }

            // await ReceiveEmoticon(e.Emoticon);

        }

        private async Task ProcessReceiveEmoticonQueue()
        {
            while (!ReceiveEmoticonQueue.IsEmpty)
            {

                ContactEmoticonTransfer transfer = null;
                if (ReceiveEmoticonQueue.TryDequeue(out transfer))
                {
                    await StoreEmoticon(transfer);
                    await Task.Delay(100);
                }
            }
            ReceiveEmoticonTask = null;
        }

        private void SetUserSettings(WpUserSettings settings)
        {
            this.UserSettings = settings;
            if (UserSettings.CustomEmoticons == null)
                UserSettings.CustomEmoticons = new HashSet<ContactEmoticon>();
            if (UserSettings.IgnoredContacts == null)
                UserSettings.IgnoredContacts = new List<string>();
            if (UserSettings.OpenChats == null)
                UserSettings.OpenChats = new List<OpenChat>();
            if (UserSettings.BlockedGroups == null)
                UserSettings.BlockedGroups = new List<string>();

            Messenger.Default.Send(
                new NotificationMessage<ClientStatus>(
                    this, settings.ManualClientStatus.GetValueOrDefault(ClientStatus.Available), ViewModelContactList.MessengerCommands.UpdateManualClientStatus.ToString()));
        }

        public void AcknowledgeSendtMessageReceipt(int id)
        {
            this.SentMessages.Remove(id);
        }
        async void Connection_OnDisconnected(object sender, EventArgs e)
        {
            await DispatcherHelper.RunAsync(() =>
              {
                  var viewModelLocator = VmHelper.Locator;

                  ClearConnectedClients();
                  IsLoggedIn = false;
                  StopReceiptTimer();

                  if (!IsRetrying)
                  {
                      if (IsUserConnecting)
                      {
                          viewModelLocator.ViewModelMainPage.Cleanup();
                      }
                      else
                      {
                          this.StartReconnectTask();
                      }
                  }

                  this.Cleanup();
                  viewModelLocator.ViewModelMainPage.RunOnNetworkDisconnect();
                  //viewModelLocator.ViewModelLoginControl.RunOnNetworkDisconnect();
                  viewModelLocator.ViewModelContactList.RunOnNetworkDisconnect();
                  IsUserConnecting = false;

              });
        }

        public void StopReconnectTask()
        {
            if (IsRetrying)
                _stopReconnectThread = true;
        }
        public void StartReconnectTask()
        {
            ClearConnectedClients();
            if (!StaticMethods.GetIsInternetAvailable())
            {
                Messenger.Default.Send(new JiveToastMessage("Internet not available"));
                return;
            }
            Task t = Task.Factory.StartNew(async () =>
            {
                if (Application.Current == null)
                    return;
                var viewModelLocator = VmHelper.Locator;
                try
                {
                    for (int count = 0; count < StaticSettings.ReconnectRetryCount; count++)
                    {
                        if (StopReconnectRetrying)
                        {
                            StopReconnectRetrying = false;
                            return;
                        }
                        int sleepTime = 2 + (5 * count);
                        while (true)
                        {
                            if (this._stopReconnectThread)
                            {
                                this._stopReconnectThread = false;
                                this.IsRetrying = false;
                                //  viewModelLocator.ViewModelLoginControl.LoginControlStatusText = "";
                                return;
                            }
                            //   viewModelLocator.ViewModelLoginControl.LoginControlStatusText = String.Format("Retrying in {0} seconds.", sleepTime.ToString(CultureInfo.InvariantCulture));
                            if (sleepTime <= 0)
                            {
                                break;
                            }
                            sleepTime--;
                            using (EventWaitHandle tmpEvent = new ManualResetEvent(false))
                            {
                                tmpEvent.WaitOne(TimeSpan.FromMilliseconds(1000));
                            }
                            //Thread.Sleep(1000);
                        }
                        //     viewModelLocator.ViewModelLoginControl.LoginControlStatusText = "Connecting";
                        IsRetrying = true;
                        //  viewModelLocator.ViewModelLoginControl.IsLoginButtonEnabled = false;
                        try
                        {

                            if (await Connection.Login(AppSettings.Instance.ServerSetting, AppSettings.Instance.UsernameSetting, CredentialsManager.GetStoredPasswordForUser(AppSettings.Instance.UsernameSetting), StaticSettings.ServerPort))
                            {
                                IsRetrying = false;
                                _queuedMessages = new Dictionary<int, Dictionary<int, ChatMessage>>();
                                _addedBatchMessages = new Dictionary<int, List<ChatMessage>>();
                                _processedMessageCount = new Dictionary<int, int>();
                                return;
                            }
                            else
                            {
                                Messenger.Default.Send(new JiveToastMessage("Login failed", "Could not connect to the server"));
                                //      viewModelLocator.ViewModelLoginControl.LoginControlStatusText = "Login failed";
                                using (EventWaitHandle tmpEvent = new ManualResetEvent(false))
                                {
                                    tmpEvent.WaitOne(TimeSpan.FromMilliseconds(1500));
                                }
                                // Thread.Sleep(1500);
                            }
                        }
                        catch (Exception e)
                        {
                            Logger.ErrorException("Error in reconnect", e);
                        }
                    }
                }
                finally
                {
                    //  viewModelLocator.ViewModelLoginControl.IsLoginButtonEnabled = true;
                }
            });
        }
        public void StartConnectTask(bool isAutoLogin)
        {
            var viewModelLocator = VmHelper.Locator;
            //  DispatcherHelper.CheckBeginInvokeOnUI(() => viewModelLocator.ViewModelLoginControl.IsLoginButtonEnabled = false);
            //    viewModelLocator.ViewModelMainWindow.IsMainMenuVisible = false;
            Task t = Task.Factory.StartNew(async () =>
            {
                try
                {
                    ClearConnectedClients();

                    Messenger.Default.Send(new NotificationMessage<string>(this, "", "SetLoginStatusText"));
                    if (await Connection.Login(AppSettings.Instance.ServerSetting, AppSettings.Instance.UsernameSetting, CredentialsManager.GetStoredPasswordForUser(AppSettings.Instance.UsernameSetting), StaticSettings.ServerPort))
                    {
                        _queuedMessages = new Dictionary<int, Dictionary<int, ChatMessage>>();
                        _addedBatchMessages = new Dictionary<int, List<ChatMessage>>();
                        _processedMessageCount = new Dictionary<int, int>();
                    }
                    else
                    {
                        //viewModelLocator.ViewModelMainPage.RunOnNetworkDisconnect();
                        //viewModelLocator.ViewModelLoginControl.RunOnNetworkDisconnect();
                        Messenger.Default.Send(new NotificationMessage<string>(this, "Connection failed", "SetLoginStatusText"));
                        if (isAutoLogin && !IsRetrying)
                        {
                            Messenger.Default.Send(new JiveToastMessage("Login failed", "Could not connect to the server"));
                            StartReconnectTask();
                        }
                        else
                        {
#if RELEASE
                            Messenger.Default.Send(new JiveDialogMessage("Login failed", "Could not connect to the server"));
#endif
                        }
                    }
                }
                catch (Exception e)
                {
                    Logger.ErrorException("Error in connect", e);
                }
            });
        }

        /// <summary>
        /// Process received ChatMessage objects
        /// </summary>
        /// <param name="header"></param>
        /// <param name="connection"></param>
        /// <param name="msg"></param>
        async void Connection_ChatMessageReceived(object s, ChatMessageEventArgs e)
        {
            await DispatcherHelper.RunAsync(() =>
             {
                 ChatMessage msg = e.Message;
                 msg.MessageRelayTime = TimeZoneInfo.ConvertTime(msg.MessageRelayTime.Value, TimeZoneInfo.Local);
                 switch (msg.MessageType)
                 {
                     case MessageType.GroupMessage:
                         {
                             //if (DispatcherHelper.UIDispatcher.HasThreadAccess)
                             DisplayGroupMessage(msg);
                             //else
                             //    await DispatcherHelper.RunAsync(() => DisplayGroupMessage(msg));
                             break;
                         }
                     case MessageType.PrivateMessage:
                         {
                             //if (DispatcherHelper.UIDispatcher.HasThreadAccess)
                             DisplayPrivateMessage(msg);
                             //else
                             //    await DispatcherHelper.RunAsync((Action)(() => ProcessPM(msg)));

                             break;
                         }
                     case MessageType.IsWriting:
                         {
                             UpdateNowWriting(msg);
                             break;
                         }
                     case MessageType.Nudge:
                         {
                             if (!UserSettings.EnableNudge.GetValueOrDefault())
                                 break;


                             var locator = VmHelper.Locator;

                             if (msg.Message == "PM")
                             {
                                 this.DisplayPrivateMessage(msg);
                             }
                             else
                             {
                                 if (msg.SenderUsername != CurrentUserInfo.UserName)
                                 {
                                     var contact = locator.ViewModelContactList.Contacts.FirstOrDefault(x => x.UserID == msg.SenderID);
                                     this.DisplayGroupMessage(msg.SenderID, string.Format("{0} just sent you a nudge.", contact.PrettyName), MessageType.Nudge, DateTime.Now, msg.RecipientID);

                                 }
                                 Messenger.Default.Send<NotificationMessage>(new NotificationMessage("Nudge"));
                             }


                             break;
                         }
                 }
             });
        }

        /// <summary>
        /// Saves info on clients who are currently typing
        /// </summary>
        private void UpdateNowWriting(ChatMessage msg)
        {
            if (CurrentUserInfo == null)
                return;
            UserInfo user = ContactClients[msg.SenderID];
            if (user.UserName == CurrentUserInfo.UserName)
                return;

            var viewModelLocator = VmHelper.Locator;//(ViewModelLocator)Application.Current.Resources["Locator"];
            IContact sender = viewModelLocator.ViewModelContactList.Contacts.FirstOrDefault(x => x.UserID == msg.SenderID);
            foreach (ViewModelChat vm in viewModelLocator.ViewModelMainPage.ChatViewModels.Where(vm => sender.ChatGroups.Any(g => g.ID == vm.ID) || sender.UserName == vm.ID))
            {
                vm.LastReceivedMessageTyping[msg.SenderID] = DateTime.Now;
            }
        }
        public ViewModelChat GetOrOpenChatViewModel(ChatMessage msg)
        {
            ViewModelChat vm = null;
            switch (msg.MessageType)
            {
                case MessageType.PrivateMessage:
                    {
                        var locator = VmHelper.Locator;
                        // UserInfo target = msg.SenderUsername == CurrentUserInfo.UserName ? CurrentUserInfo : ConnectedClients[msg.SenderID];
                        UserInfo target = msg.SenderUsername == CurrentUserInfo.UserName ? ContactClients.Values.FirstOrDefault(x => x.UserName == msg.RecipientID) : ContactClients[msg.SenderID];
                        var contact = locator.ViewModelContactList.Contacts.FirstOrDefault(x => x.UserName == target.UserName);
                        vm = (ViewModelPrivateMessage)locator.ViewModelMainPage.ChatViewModels.Union(this.ClosedConversations.Values).Where(x => x.ID == contact.UserName).FirstOrDefault();

                        if (vm == null)
                        {
                            vm = new ViewModelPrivateMessage(this, contact);
                            locator.ViewModelMainPage.ChatViewModels.Add(vm);
                        }

                        if (!locator.ViewModelMainPage.ChatViewModels.Any(x => x.ID == vm.ID))
                        {
                            locator.ViewModelMainPage.ChatViewModels.Add(vm);
                        }
                    }
                    break;
                case MessageType.GroupMessage:
                    vm = (ViewModelGroupChat)VmHelper.Locator.ViewModelMainPage.ChatViewModels.Union(this.ClosedConversations.Values).FirstOrDefault(x => x.ID == msg.RecipientID);
                    if (vm == null)
                    {
                        //var senderClient = VmHelper.Locator.ViewModelContactList.Contacts.FirstOrDefault(x => x.UserID == msg.SenderID);
                        //var group = senderClient.ChatGroups.FirstOrDefault(x => x.ID == msg.RecipientID);
                        Messenger.Default.Send(new NotificationMessageAction<IContact>(this, ViewModelContactList.MessengerCommands.GetCurrentUserContact.ToString(), contact =>
                            {
                                var group = contact.ChatGroups.FirstOrDefault(x => x.ID == msg.RecipientID);
                                if (group != null)
                                {
                                    vm = new ViewModelGroupChat(this, new JiveChatGroupModel(group.ID, group.Name));
                                    ClosedConversations[group.ID] = vm;
                                }
                                else
                                    Logger.Error(string.Format("Could not find group {0} for user {1}", group.ID, contact.UserID));
                            }));

                    }
                    break;
            }
            return vm;
        }
        /// <summary>
        /// Displays a private message.
        /// </summary>
        /// <param name="msg"></param>
        public void DisplayPrivateMessage(ChatMessage msg)
        {
            ViewModelPrivateMessage vm = (ViewModelPrivateMessage)GetOrOpenChatViewModel(msg);
            if (VmHelper.Locator.ViewModelMainPage.CurrentActiveViewModel != vm)
                vm.PmTarget.UnreadMessageCount++;
            vm.DisplayMessage(CreateDisplayMessage(msg, true));
        }

        /// <summary>
        /// Opens a private message window to the specified target user
        /// </summary>
        /// <param name="target"></param>
        public void OpenNewPM(string userID)
        {
            var locator = VmHelper.Locator;//(ViewModelLocator)Application.Current.Resources["Locator"];

            var contact = locator.ViewModelContactList.Contacts.FirstOrDefault(x => x.UserID == userID);

            ViewModelPrivateMessage vm = (ViewModelPrivateMessage)locator.ViewModelMainPage.ChatViewModels.Where(x => x.ID == contact.UserName).FirstOrDefault();
            if (vm == null)
            {
                vm = new ViewModelPrivateMessage(this, contact);
                locator.ViewModelMainPage.ChatViewModels.Add(vm);
            }

            locator.ViewModelMainPage.CurrentActiveViewModel = vm;
            locator.ViewModelMainPage.SelectedPivotItemIndex = 0;
        }

        public async Task ChangeManualClientStatus(ClientStatus status)
        {
            if (CurrentUserInfo == null)
                return;
            UserSettings.ManualClientStatus = status;
            CurrentUserInfo.ClientStatus = status;
            await AddOrUpdateUsers(CurrentUserInfo);
            await Connection.SendCommand(new DeviceStatus(CurrentUserInfo.UserID, status), ClientCommands.DeviceStatusUpdate).ConfigureAwait(false);

        }


        private Task<bool> AddOrUpdateUsers(UserInfo user, bool ProcessEmoticonsAndAvatar = true, bool createVisualGroups = true)
        {
            if (user == null)
                return Task.FromResult(false);
            if (user.ChatGroups == null)
                user.ChatGroups = new List<ChatGroup>();

            if (user.TimestampAfk.HasValue)
            {
                if (user.TimestampAfk.Value.Offset.TotalMinutes == 0)
                    user.TimestampAfk = user.TimestampAfk.Value.ToLocalTime();
            }

            if (user.TimestampOffline.HasValue)
            {
                if (user.TimestampOffline.Value.Offset.TotalMinutes == 0)
                    user.TimestampOffline = user.TimestampOffline.Value.ToLocalTime();
            }
            UserInfo oldUser;
            bool isNewConnection = false;
            if (ContactClients.TryGetValue(user.UserID, out oldUser))
            {
                oldUser.ClientStatus = user.ClientStatus;
                //this means we have it, so we just go through the properties and update what we can.
                var userInfoProperties = oldUser.GetType().GetRuntimeProperties().Where(prop => prop.CanRead && prop.CanWrite);
                foreach (var prop in userInfoProperties)
                {
                    var value = prop.GetValue(user, null);
                    var attributes = prop.GetCustomAttributes(typeof(UserInfoAttribute), false).ToList();
                    if (attributes.Count > 0)
                    {
                        UserInfoAttribute att = attributes[0] as UserInfoAttribute;
                        if (att == null)
                            continue;

                        else if (value != null)
                        {
                            //if (att.PropertyType == UserInfoAttributeType.Avatar)
                            //{
                            //    if (((byte[])value).Length == 0)
                            //        value = null;
                            //}
                            try
                            {
                                prop.SetValue(oldUser, value, null);
                            }
                            catch (Exception err)
                            {
                                Logger.ErrorException("Error setting user settings property", err);
                            }
                        }
                    }
                }
            }
            else
            {
                ContactClients.Add(user.UserID, user);
                isNewConnection = true;
            }

            var viewModelLocator = VmHelper.Locator;

            if (user.Equals(this.CurrentUserInfo))
            {
                this.UserSettings.CustomEmoticons = user.CustomEmoticons;
            }
            //using (TimeIt t = new TimeIt("AddOrUpdateContact " + user.UserName))
            //{
            viewModelLocator.ViewModelContactList.AddOrUpdateContact(user, createVisualGroups);
            //}
            if (isNewConnection && user.ClientStatus != ClientStatus.Offline && ProcessEmoticonsAndAvatar)
            {
                //await ProcessCustomContactEmoticons(user).ConfigureAwait(false);

                //await ProcessAvatar(user).ConfigureAwait(false);
                if (!AvatarAndEmoticonCheckQueue.Any(x => x.UserName == user.UserName))
                    AvatarAndEmoticonCheckQueue.Enqueue(user);
                lock (_lockObject)
                {
                    if (AvatarAndEmoticonCheckTask == null)
                    {
                        AvatarAndEmoticonCheckTask = Task.Run(() => ProcessAvatarAndEmoticonCheckQueue());
                    }
                }
            }
            return Task.FromResult(true);
        }
        private async Task ProcessAvatarAndEmoticonCheckQueue()
        {
            _cts = new CancellationTokenSource();

            if (AvatarAndEmoticonCheckQueue.Count > 0)
                await StatusBarHelper.ShowStatusBarProgress("Processing custom emoticons");
            int n = 0;
            int max = AvatarAndEmoticonCheckQueue.Count;
            while (!AvatarAndEmoticonCheckQueue.IsEmpty)
            {
                n++;
                UserInfo user = null;
                StatusBarHelper.UpdateStatusBarProgress((double)n / (double)max);
                if (AvatarAndEmoticonCheckQueue.TryDequeue(out user))
                {
                    if (user.ClientStatus != ClientStatus.Offline)
                    {
                        Debug.WriteLine("Processing custom emoticons for " + user.UserName);
                        await ProcessCustomContactEmoticons(user, _cts.Token).ConfigureAwait(false);
                    }
                    Debug.WriteLine("Processing avatar for " + user.UserName);
                    await ProcessAvatar(user).ConfigureAwait(false);
                    if (user.Equals(this.CurrentUserInfo))
                    {
                        Task.Run(async () =>
                            {
                                await Task.Delay(3000);
                                await DispatcherHelper.RunAsync(() =>
                                    {
                                        Messenger.Default.Send(new NotificationMessage(this, "UpdateCustomEmoticons"));
                                    });
                            });
                    }
                }
            }
            StatusBarHelper.HideStatusBarProgress();
            AvatarAndEmoticonCheckTask = null;
        }
        private async Task AddOrUpdateUsers(IEnumerable<UserInfo> users, bool processAvatarAndEmoticons)
        {
            List<string> performedEmoticonAndAvatarCheck = new List<string>();
            string lastUserID = users.Last().UserID;
            Debug.WriteLine("last userID is: " + lastUserID);
            foreach (UserInfo user in users)
            {
                using (TimeIt t = new TimeIt("add user " + user.UserID))
                {
                    bool checkEmoticonsAndAvatar = processAvatarAndEmoticons && !performedEmoticonAndAvatarCheck.Contains(user.UserName);
                    bool createVisualGroups = user.UserID == lastUserID;
                    await AddOrUpdateUsers(user, checkEmoticonsAndAvatar, createVisualGroups);
                    if (checkEmoticonsAndAvatar)
                        performedEmoticonAndAvatarCheck.Add(user.UserName);
                }
            }
            List<UserInfo> connectedClients = new List<UserInfo>(this.ContactClients.Values);

            var viewModelLocator = (ViewModelLocator)Application.Current.Resources["Locator"];

            foreach (var user in connectedClients)
            {
                if (!users.Contains(user) && !user.Equals(CurrentUserInfo))
                {
                    this.ContactClients.Remove(user.UserID);
                    viewModelLocator.ViewModelContactList.Contacts.Remove(viewModelLocator.ViewModelContactList.Contacts.FirstOrDefault(x => x.UserID == user.UserID));
                }
            }
            if (!_receivedClientList)
            {
                viewModelLocator.ViewModelMainPage.RestoreTabs();
                //Messenger.Default.Send(new NotificationMessage(this, "RefreshContactsFilter"));
                this._receivedClientList = true;
            }
        }
        private void RemoveUser(string userID)
        {

            var viewModelLocator = VmHelper.Locator;
            UserInfo user = null;
            if (ContactClients.TryGetValue(userID, out user))
            {
                // don't remove our own connected user.
                if (!userID.Equals(CurrentUserInfo.UserID))
                {
                    // ContactClients.Remove(user.UserID);
                    viewModelLocator.ViewModelContactList.SetContactAsOffline(user.UserID);
                }
                var chats = viewModelLocator.ViewModelMainPage.ChatViewModels.Union(this.ClosedConversations.Values).Where(x => x is ViewModelChat && (user.ChatGroups.Any(y => y.ID == x.ID) || x.ID == user.ID)).Cast<ViewModelChat>();
                foreach (var vm in chats)
                {
                    vm.DisplayMessage(new DisplayMessage(viewModelLocator.ViewModelContactList.Contacts.FirstOrDefault(x => x.UserID == userID), user.PrettyName + " has left the conversation!", DisplayMessageOptions.LogoutOptions, DateTime.Now, true));
                }
            }
        }

        /// <summary>
        /// Stores a custom contact emoticon to storage.
        /// </summary>
        /// <param name="msg"></param>
        private async Task<StorageFile> StoreEmoticon(ContactEmoticonTransfer ce)
        {

            UserInfo sender = ContactClients[ce.SenderID];
            Debug.WriteLine(string.Format("Received emoticon from {0} for key {1}", ce.SenderID, ce.KeyWord));
            if (String.IsNullOrEmpty(ce.KeyWord))
                return null;

            try
            {
                StorageFolder folder = await StaticMethods.GetEmoticonStorageFolderForUser(sender.UserName).ConfigureAwait(false);

                StorageFile file = await StaticMethods.SaveImageToStorage(folder, ce.EmoticonByteArray, ce.Hash + ce.Extension).ConfigureAwait(false);
                if (file != null)
                {
                    var validatedEmoticons = AppSettings.Instance.ValidatedEmoticons;
                    validatedEmoticons.Add(ce.Hash);
                    AppSettings.Instance.ValidatedEmoticons = validatedEmoticons;
                    //this.EmoticonExistsCache[sender].Add(ce.KeyWord);
                }
                return file;

            }
            catch (IOException err)
            {
                Messenger.Default.Send(String.Format("Error saving custom emoticon from {0} : ", sender.UserName) + err.Message);
                return null;
            }


        }
        /// <summary>
        /// Process avatar and request it if missing.
        /// </summary>
        /// <param name="user"></param>
        private async Task ProcessAvatar(UserInfo user)
        {
            if (user.Avatar == null)
                return;
            if (string.IsNullOrEmpty(user.Avatar.Hash))
                return;
            bool requestAvatar = true;
            try
            {
                //StorageFolder folder = await StaticMethods.GetAvatarStorageFolderForUser(user.UserName).ConfigureAwait(false);
                //StorageFile file = await folder.GetFileAsync(user.Avatar.Hash + user.Avatar.Extension);
                Uri uri = new Uri("ms-appdata:///local/" + user.UserName + "/Avatars/" + user.Avatar.Hash + user.Avatar.Extension);
                StorageFile file = await StorageFile.GetFileFromApplicationUriAsync(uri);

                if (file != null)//StaticMethods.FileExistsSync(path,await StaticMethods.GetAvatarStorageFolderForUser(user.UserName)))
                {
                    byte[] data = await StaticMethods.LoadImageData(file).ConfigureAwait(false);
                    string hash = await StaticMethods.ComputeMD5Async(data).ConfigureAwait(false);

                    if (hash == user.Avatar.Hash)
                        requestAvatar = false;
                }
            }
            catch (Exception ex)
            {
                Logger.ErrorException("Error checking if emoticon file exists", ex);
            }
            if (requestAvatar)
            {
                await Connection.SendCommand(new AvatarRequest(user.Avatar.Hash, user.Avatar.Extension, user.ID, user.UserName), ClientCommands.RequestAvatar);
                Logger.Info(string.Format("requesting avatar download for user {0}", user.UserName));
            }

        }

        /// <summary>
        /// Process custom emoticons, and request missing images.
        /// </summary>
        /// <param name="message"></param>
        private async Task ProcessCustomContactEmoticons(UserInfo user, CancellationToken token)
        {
            if (user.CustomEmoticons == null)
                return;
            //if (!this.EmoticonExistsCache.ContainsKey(user))
            //    this.EmoticonExistsCache[user] = new List<string>();
            var validatedEmoticons = AppSettings.Instance.ValidatedEmoticons;
            foreach (ContactEmoticon ce in user.CustomEmoticons)
            {
                if (token.IsCancellationRequested)
                    break;
                if (validatedEmoticons.Contains(ce.Hash))
                    continue;
                bool requestEmoticon = true;
                string keyword = ce.Keyword;
                //if (!this.EmoticonExistsCache[user].Contains(keyword))
                //{
                try
                {
                    Uri uri = new Uri("ms-appdata:///local/" + user.UserName + "/CustomEmoticons/" + ce.Hash + ce.Extension);

                    //  StorageFolder emoticonFolder = await StaticMethods.GetEmoticonStorageFolderForUser(user.UserName);
                    //  requestEmoticon = !StaticMethods.FileExistsSync(ce.Hash + ce.Extension, emoticonFolder);// !File.Exists(StaticMethods.CombinePath(StaticSettings.CustomContactEmoticonFolder, sender.UserName, kvp.Value));
                    // requestEmoticon = !StaticMethods.FileExistsSync(uri);

                    StorageFile file = await StorageFile.GetFileFromApplicationUriAsync(uri);
                    if (file != null)
                    {
                        var properties = await file.GetBasicPropertiesAsync();
                        if (properties.Size > 0)
                        {
                            validatedEmoticons.Add(ce.Hash);
                            requestEmoticon = false;
                        }
                    }
                    //byte[] data = await StaticMethods.LoadImageData(file).ConfigureAwait(false);
                    //string hash = await StaticMethods.ComputeMD5Async(data).ConfigureAwait(false);

                    //if (hash == ce.Hash)
                    //    requestEmoticon = false;

                    //if (!requestEmoticon)
                    //    this.EmoticonExistsCache[user].Add(keyword);
                }
                catch (Exception ex)
                {
                    Logger.ErrorException("Error checking if emoticon file exists", ex);
                }
                //}
                if (requestEmoticon)
                {
                    //Send a message to the server requesting the emoticon so we can save it to disk
                    await Connection.SendEmoticonRequest(new EmoticonRequest(keyword, user.ID, user.UserName));
                }
                await Task.Delay(150);
            }
            DispatcherHelper.RunAsync(() => AppSettings.Instance.ValidatedEmoticons = validatedEmoticons);


        }

        private void DisplayGroupMessage(string senderID, string messageText, MessageType mt, DateTime timestamp, string recipientID)
        {
            JiveContactModel sender = VmHelper.Locator.ViewModelContactList.Contacts.FirstOrDefault(x => x is JiveContactModel && x.UserID == senderID) as JiveContactModel;
            ChatMessage msg = new ChatMessage(sender.DeviceID, sender.UserName, mt, messageText, recipientID)
            {
                MessageRelayTime = timestamp,
            };
            DisplayGroupMessage(msg);

        }

        private void DisplayGroupMessage(UserInfo sender, string messageText, DateTime timestamp, DisplayMessageOptions displayMessageOptions = null)
        {
            ServerInfoMessage msg = new ServerInfoMessage
            {
                Message = messageText,
                UserInfo = sender
            };
            DisplayGroupMessage(msg, displayMessageOptions);
        }

        public void DisplayGroupMessage(ChatMessage msg)
        {
            ViewModelGroupChat vm = (ViewModelGroupChat)GetOrOpenChatViewModel(msg);
            vm.DisplayMessage(CreateDisplayMessage(msg, true));
            Messenger.Default.Send(new NotificationMessageAction<IContact>(this, ViewModelContactList.MessengerCommands.GetCurrentUserContact.ToString(), contact =>
            {
                JiveChatGroupModel grp = contact.ChatGroups.FirstOrDefault(x => x.ID == msg.RecipientID) as JiveChatGroupModel;
                if (grp != null)
                {
                    grp.UnreadMessageCount++;
                }
            }));
        }

        //private void DisplayChatMessage(ChatMessage msg, ViewModelChat vm)
        //{
        //    // ViewModelGroupChat vm = (ViewModelGroupChat)VmHelper.Locator.ViewModelMainPage.ChatViewModels.Union(this.ClosedConversations.Values).FirstOrDefault(x => x.ID == msg.RecipientID);


        //    bool isLastInBatch = msg.MessageQueueInfo == null || msg.MessageQueueInfo.QueuedMessageCount > 10 && (msg.MessageQueueInfo.QueuedMessageCount == msg.MessageQueueInfo.QueuedMessageNumber);
        //    vm.DisplayMessage();

        //}
        private DisplayMessage CreateDisplayMessage(ChatMessage msg, bool isLastInBatch)
        {
            DisplayMessage displayMessage = null;
            Messenger.Default.Send(new NotificationMessageAction<IContact>(this, msg.SenderUsername, "GetContactFromUsername", contact =>
                {
                    //var contact = VmHelper.Locator.ViewModelContactList.Contacts.FirstOrDefault(x => x.UserID == msg.SenderID);
                    displayMessage = CreateDisplayMessage(msg, contact, isLastInBatch);
                }));
            return displayMessage;
        }
        private DisplayMessage CreateDisplayMessage(ChatMessage msg, IContact sender, bool isLastInBatch)
        {
            var displayMessageOptions = DisplayMessageOptions.ChatMessageOption;
            if (msg.MessageType == MessageType.Nudge)
                displayMessageOptions = DisplayMessageOptions.NudgeOptions;
            var displayMessage = new DisplayMessage(sender, msg.Message, displayMessageOptions, msg.MessageRelayTime.Value, isLastInBatch);
            return displayMessage;
        }
        private void DisplayGroupMessage(ServerInfoMessage msg, DisplayMessageOptions displayOptions = null)
        {
            var chats = VmHelper.Locator.ViewModelMainPage.ChatViewModels.Union(this.ClosedConversations.Values).Where(x => x.GetType() == typeof(ViewModelGroupChat) && msg.UserInfo.ChatGroups.Any(y => y.ID == x.ID));

            foreach (ViewModelGroupChat vm in chats)
            {
                UserInfo sender = CurrentUserInfo;
                ((ViewModelGroupChat)vm).DisplayMessage(new DisplayMessage(VmHelper.Locator.ViewModelContactList.CurrentUser, msg.Message, displayOptions ?? DisplayMessageOptions.ServerStatusMessageOption, DateTime.Now, true));
            }
        }

        public Dictionary<string, IEmoticon> GetEmoticons(string userID, bool includeDefault, bool includeCustom, bool includeLineAndSpacer)
        {
            Dictionary<string, IEmoticon> allEmoticons = new Dictionary<string, IEmoticon>();
            if (includeLineAndSpacer)
                allEmoticons.AddRange(this.SymbolEmoticons);

            //if (CurrentUserInfo == null)
            //    return allEmoticons;
            IContact user = null;//VmHelper.Locator.ViewModelContactList.Contacts.FirstOrDefault(x => x is JiveContactModel && x.UserID == userID) as JiveContactModel;//null;
            Messenger.Default.Send(new NotificationMessageAction<IContact>(this, userID, "GetContactFromUserID", cont =>
                {
                    user = cont;
                }));
            //if (user == null)
            //    return allEmoticons;

            if (includeCustom && user != null)
            {
                if (user.CustomEmoticons != null)
                {
                    foreach (JiveCustomEmoticonModel emoticon in user.CustomEmoticons)
                    {
                        allEmoticons.Add(emoticon.EmoticonShortcut, emoticon);
                    }
                }
            }
            if (includeDefault)
            {
                foreach (JiveDefaultEmoticonModel model in DefaultEmoticons)
                {
                    foreach (string s in model.EmoticonShortcut)
                    {
                        string key = s.ToLower();
                        if (!allEmoticons.ContainsKey(key))
                            allEmoticons.Add(key, model);
                        if (!allEmoticons.ContainsKey(s))
                            allEmoticons.Add(s, model);
                    }
                }
            }
            return allEmoticons;
        }

        //private async Task StartEmoticonExistsCheckAsync(Dictionary<string, string> emoticons, UserInfo user)
        //{
        //    List<string> toRemove = new List<string>();
        //   List<string> validatedEmoticons = AppSettings.Instance.ValidatedEmoticons;
        //    //Dictionary<string, string> customEmoticons = obj as Dictionary<string, string>;
        //    foreach (var kvp in emoticons)
        //    {
        //        if (validatedEmoticons.Contains(kvp.Value))
        //            continue;
        //        if (StaticMethods.FileExistsSync(kvp.Value, await StaticMethods.GetEmoticonStorageFolderForUser(user.UserName)))//!File.Exists(kvp.Value))
        //            validatedEmoticons.Add(kvp.Value);
        //        else
        //            toRemove.Add(kvp.Key);
        //    }
        //    foreach (string str in toRemove)
        //        emoticons.Remove(str);

        //    AppSettings.Instance.ValidatedEmoticons = validatedEmoticons;
        //}

        public bool ValidateEmoticonShortcut(string str, out string error)
        {
            return ValidateEmoticonShortcut(str, string.Empty, out error);
        }

        public bool ValidateEmoticonShortcut(string str, string exludeShortcut, out string error)
        {
            error = string.Empty;
            List<string> keys = GetEmoticons(CurrentUserInfo.UserID, true, true, true).Keys.ToList();
            if (!String.IsNullOrEmpty(exludeShortcut))
            {
                if (keys.Contains(exludeShortcut))
                    keys.Remove(exludeShortcut);
            }
            if (str.Length < 2)
            {
                error = "Minimum 2 characters";
                return false;
            }
            if (str.Length > 10)
            {
                error = "Max 10 characters";
                return false;
            }
            string conflictedKey = keys.FirstOrDefault(x => str.Contains(x) || x.Contains(str));
            if (!string.IsNullOrEmpty(conflictedKey))
            {
                error = "Shortcut already in use or conflicted: " + conflictedKey;
                return false;
            }
            return true;
        }

        public async Task ResetTileCount()
        {
            //BadgeNumericNotificationContent badgeContent = new BadgeNumericNotificationContent((uint)0);

            //BadgeUpdateManager.CreateBadgeUpdaterForApplication().Update(badgeContent.CreateNotification());
            BadgeUpdateManager.CreateBadgeUpdaterForApplication().Clear();
            TileUpdateManager.CreateTileUpdaterForApplication().Clear();

            //   BadgeUpdateManager.CreateBadgeUpdaterForApplication().Update(new BadgeNumericNotificationContent((uint)0).CreateNotification());
            await Connection.SendMessage(new ChatMessage(CurrentUserInfo, MessageType.ResetUnreadMessageCount));
        }

        public void Cleanup()
        {
            if (_cts != null)
                _cts.Cancel();
            _receivedClientList = false;
            ClearConnectedClients();
            ClosedConversations.Clear();
            FriendUsernames.Clear();
            //EmoticonExistsCache.Clear();
            VmHelper.Locator.ViewModelNotifications.Cleanup();
            StopReceiptTimer();
        }

        protected virtual void RaisePropertyChanged(string propertyName)
        {
            PropertyChangedEventHandler handler = PropertyChanged;
            if (handler != null) handler(this, new PropertyChangedEventArgs(propertyName));
        }
        public event PropertyChangedEventHandler PropertyChanged;
    }
}
